<?php
namespace tp5_lib\access\logic;

use tp5_lib\common\logic\Logic;

/**
 * 权限
 */
class PrivilegeLogic extends Logic
{
    static protected $_instance=null;
    /** @return self */
    static public function instance()
    {
        if(static::$_instance === null) static::$_instance = new self;
        return static::$_instance;
    }
    /**
     * 获取已有权限
     * @return array 索引数组形式
     */
    public function getPrivileges()
    {
        if(empty($this->_privileges)) $this->_privileges=session("access");
        return $this->_privileges;
    }
    /**
     * 设置权限
     * @param array $privileges
     * @param bool $save 是否保存到session中
     */
    public function setPrivileges($privileges,$save=true)
    {
        if($save) session("access",$privileges);
        $this->_privileges=$privileges;
        return $this;
    }
    /**
     * 清除用户权限缓存
     */
    public function clear()
    {
        $this->_privileges=null;
        session("access",null);
        return $this;
    }
    /**
     * 将字符串表示的权限转换成数组形式
     * @param string $privileges 权限
     * @param array $record 原有的权限数组
     */
    public function parse($privileges,$record=[])
    {
        if(empty($record)) $record=[];
        $list=explode(",",$privileges);
        foreach($list as $item){
            $modifier="";
            if(in_array($item{0},["+","-"])){
                $modifier=$item{0};
                $item=trim(substr($item,1));
            }
            //只有原来修饰符是`+`,现在是``的情况，才会被反向覆盖;其他情况直接覆盖
            if(isset($record[$item]) && $modifier==="" && $record[$item]==="+") $modifier="+";
            $record[$item]=$modifier;
        }
        return $record;
    }
    /**
     * 将权限列表字符串化
     */
    public function stringify($list=null)
    {
        if($list===null) $list=$this->getPrivileges();
        if(!empty($list) && is_array($list)){
            $result=[];
            foreach($list as $privilege => $modifier){
                $result[]=$modifier.$privilege;
            }
            return implode(",",$result);
        }
        return "";
    }

    /**
     * 进行授权
     * 加载用户和角色的权限
     * @param string $accesss 权限，多个权限以`,`分隔
     * @return self
     */
    public function accredit($access)
    {
        if(empty($access)) return $this;
        $record=$this->getPrivileges();
        $result=$this->parse($access,$record);
        $this->setPrivileges($result);
        return $this;
    }
    /**
     * 权限检查
     * @param string $accesss 权限，可以是`&`和`|`分隔的多条
     * @return bool
     */
    public function check($access)
    {
        if(empty($access)) return true;
        if(!$record=$this->getPrivileges()) return false;
        $or_list=explode("|",$access);
        foreach($or_list as $or_item){
            if($this->checkAndAccess($or_item,$record)) return true;
        }
        return false;
    }
    /**
     * 权限检查-多个条件与
     * @param string $access 需要的权限，可以是`&`分隔的多条
     * @param string $privileges 拥有的权限，可以是多条
     * @return bool
     */
    protected function checkAndAccess($access,$privileges)
    {
        $and_list=explode("&",$access);
        foreach($and_list as $and_item){
            if($this->checkAccessItem($and_item,$privileges) ===false) return false;
        }
        return true;
    }
    /**
     * 权限检查-单个条件
     * @param string $access 需要的权限，必须是单条
     * @param string $privileges 拥有的权限，可以是多条
     * @return bool
     */
    protected function checkAccessItem($access,$privileges)
    {
        $matchs=[];
        foreach($privileges as $privilege => $modifier){
            if($this->checkItem($access,$privilege)) $matchs[strlen($privilege)]=$modifier;
        }
        if(!$matchs) return false;

        //根据权限的有效范围排序找出实际生效的权限
        //实际上是根据权限名称的长度反向排序的，在这里长度与范围大小相反
        //没有修饰符的权限没有优先级，除非是唯一一项，否则不会生效
        krsort($matchs);
        foreach($matchs as $match){
            if($match === "+") return true;
            elseif($match === '-') return false;
        }
        return true;
    }
    /**
     * 逐条检查用户拥有的权限是否允许通行
     * 检查结果只代表单对单的权限，多对多需要额外的逻辑处理
     * @param string $access 需要的权限，必须是单条
     * @param string $privilege 拥有的权限，必须是单条
     * @return bool
     */
    protected function checkItem($access,$privilege)
    {
        if($privilege === "*") return true;
        //如果条件以`.*`结尾
        if(preg_match("/\.\*$/",$privilege)){
            $privilege = preg_replace("/\*$/",'',$privilege);
            return strpos($access,$privilege) === 0;
        }
        else{
            if($access === $privilege) return true;
            $privilege .= ".";
            return strpos($access,$privilege) === 0;
        }
    }
    /**
     * 获取全部权限目录以及是否有权限
     * 每个项目的status对应:0,无;1,有;2,*;3,+;4,-;
     */
    public function toMenu($list=null)
    {
        $module=new AccessModule();
        $menu=$module->read("access");
        foreach($menu as $key_1 => $item_1){
            $menu[$key_1]["status"]=0;
            $menu[$key_1]["code"]=$key_1;
            foreach($item_1["sub"] as $key_2 => $item_2){
                $menu[$key_1]["sub"][$key_2]["status"]=0;
                $menu[$key_1]["sub"][$key_2]["code"]=$key_1 ."__". $key_2;
            }
        }

        if($list===null) $list=$this->getPrivileges();
        foreach($list as $key => $value){
            unset($key_2);
            $status=0;
            $keys=explode(".",$key);
            $key_1=$keys[0];

            if(isset($keys[1])){
                if($keys[1] === "*"){
                    $status=2;
                }
                else{
                    $key_2=$keys[1];
                }
            }

            if($value === ""){
                $status=1;
            }
            elseif($value === "+"){
                $status=3;
            }
            elseif($value === "-"){
                $status=4;
            }

            if(isset($key_2)){
                if(isset($menu[$key_1]["sub"][$key_2]["status"])) $menu[$key_1]["sub"][$key_2]["status"]=$status;
            }
            else{
                if(isset($menu[$key_1]["status"])) $menu[$key_1]["status"]=$status;
            }
        }
        return $menu;
    }
    /**
     * 从目录中提取出权限字符串
     */
    public function fromMenu($data)
    {
        $privileges=[];
        foreach($data as $name => $status){
            $name=str_replace("__",".",$name);
            if($status == 0) continue;
            elseif($status ==1) $privileges[]=$name;
            elseif($status ==2) $privileges[]=$name.".*";
            elseif($status ==3) $privileges[]="+".$name;
            elseif($status ==4) $privileges[]="-".$name;
        }
        return implode(",",$privileges);
    }
}
